/**
 * Project of Vehicle Routing Problem with Time Windows implements 
 * some of well-known algorithms for solving VRPTW and presents new 
 * one.
 *
 * @author Michal Drobny  
 */

package logic.route;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import util.Cloneable;
import logic.destination.Destination;

/**
 * Represents a route created from list of paths. From list of paths we can
 * constructs the whole route like list of destinations.
 * 
 * @author Michal Drobny
 * @date 16.10.2014
 */
public class RouteFromPaths implements Route, Cloneable<RouteFromPaths> {

	private List<Path> paths;

	/**
	 * Constructor.
	 */
	public RouteFromPaths() {
		super();
		this.paths = new Vector<Path>();
	}

	/**
	 * @return the paths
	 */
	public List<Path> getPaths() {
		return paths;
	}

	/**
	 * Adds given path to the list of paths.
	 * 
	 * @param path
	 *            The path
	 */
	public void addPath(Path path) {
		paths.add(path);
	}

	@Override
	public List<Destination> getDestinations() {

		Set<Destination> set = new HashSet<Destination>();
		for (Path path : paths) {
			set.add(path.getStart());
			set.add(path.getTarget());
		}
		List<Destination> result = new ArrayList<Destination>();
		result.addAll(set);
		return result;
	}

	/**
	 * This method inserts a new path to the route and removes paths, which are
	 * dependent on that path. Usually this method is used by Clarke-Wright
	 * method, when we need to update route depending on choosen evaluation
	 * number. Depending on boolean parameter createNew will be this method
	 * updating this origin route or creates a new route.
	 * 
	 * @param createNew
	 *            Whether this route will be modified or will be created new
	 *            route
	 * @param dest
	 *            The start destination of new inserted path
	 * @param to
	 *            The target destination of new inserted path
	 * @return The route result - depending on boolean parameter createNew will
	 *         be new route or not
	 */
	public RouteFromPaths updateRoute(boolean createNew, Destination dest,
			Destination to, Destination start) {

		if (dest == null || to == null || start == null)
			return null;

		RouteFromPaths route;
		if (createNew)
			route = (RouteFromPaths) this.clone();
		else
			route = this;

		Iterator<Path> iterator = route.paths.iterator();
		while (iterator.hasNext()) {
			Path path = iterator.next();
			if (path.getStart().equals(dest) || path.getTarget().equals(to))
				iterator.remove();
		}
		route.addPath(new Path(dest, to));
		
		if (route.getCycles(start) == null)
			return null;
		
		return route;
	}

	/**
	 * Gets the cycles in this route depending on the starting destination.
	 * 
	 * @return the cycles in this route depending on the starting destination
	 */
	public Cycle[] getCycles(Destination startDestination) {

		if (!getDestinations().contains(startDestination))
			return null;

		List<Path> pathsFromStart = new ArrayList<Path>();
		Map<Destination, Destination> paths = new HashMap<Destination, Destination>();
		for (Path path : getPaths()) {
			if (path.getStart().equals(startDestination))
				pathsFromStart.add(path);
			else
				paths.put(path.getStart(), path.getTarget());
		}

		List<Cycle> cycles = new ArrayList<Cycle>();
		for (Path pathFromStart : pathsFromStart) {
			Cycle cycle = new Cycle();	
			cycle.addToEnd(startDestination);
			Destination from = pathFromStart.getTarget();

			while (!from.equals(startDestination)) {
				if (paths.containsKey(from)) {
					cycle.addToEnd(from);
					Destination oldFrom = from;
					from = paths.get(from);
					paths.remove(oldFrom);
				} else {
					return null;
				}
			}
			cycles.add(cycle);
		}

		if (!paths.isEmpty())
			return null;

		return cycles.toArray(new Cycle[cycles.size()]);
	}

	@Override
	public RouteFromPaths clone() {
		RouteFromPaths clonedRoute = new RouteFromPaths();
		for (Path path : this.paths)
			clonedRoute.addPath(path.clone());
		return clonedRoute;
	}

	@Override
	public Destination[] getOrderedDestinations(Destination start) {
		if (!getDestinations().contains(start))
			return null;

		List<Path> pathsFromStart = new ArrayList<Path>();
		Map<Destination, Destination> paths = new HashMap<Destination, Destination>();
		for (Path path : getPaths()) {
			if (path.getStart().equals(start))
				pathsFromStart.add(path);
			else
				paths.put(path.getStart(), path.getTarget());
		}

		List<Destination> destinations = new ArrayList<Destination>();
		for (Path pathFromStart : pathsFromStart) {
			destinations.add(start);
			Destination from = pathFromStart.getTarget();

			while (!from.equals(start)) {
				if (paths.containsKey(from)) {
					destinations.add(from);
					Destination oldFrom = from;
					from = paths.get(from);
					paths.remove(oldFrom);
				} else {
					return null;
				}
			}
		}

		if (!paths.isEmpty())
			return null;

		return destinations.toArray(new Destination[destinations.size()]);
	}
}